/**
 * Node 类的扩展
 */

import { Component, Node, __private } from 'cc';

export type TCompConstructor<T> = __private._types_globals__Constructor<T> | __private._types_globals__AbstractedConstructor<T>;

declare module 'cc' {

    interface Node {
        /**
         * 通过名称查找子节点
         * 注意：重名会导致获取的节点不对
         * @param name 
         * @returns 
         */
        findChildByName: (name: string) => Node | null;

        /**
         * 通过名称数组查找子节点，并返回子节点字典
         * 注意：重名会导致获取的节点不对
         * @param nameArr 
         * @returns 
         */
        findChildMapByNameArr: (nameArr: string[]) => Map<string, Node>;

        /**
         * 通过名称查找子节点
         * 注意：重名会导致获取的组件不对
         * 使用举例：
         *      const spr = this.node.findCompByName('iconImg', Sprite)!;
         * @param childName 
         * @param compType 
         * @returns 
         */
        findCompByName: <T extends Component>(childName: string, compType: TCompConstructor<T>) => T | null | undefined;

        /**
         * 批量查找子节点上的组件
         * 注意：重名会导致获取的组件不对
         * 使用举例：
         *      const compTypeMap = new Map<string, TCompConstructor<Component>>([
         *          ['Node_1', UITransform],
         *          ['spr_2', Sprite],
         *      ]);
         *      const compMap = this.node.findCompMap(compTypeMap);
         *      this._uiTrans = compMap.get('Node_1')! as UITransform;
         *      this._spr = compMap.get('spr_2')! as Sprite;
         * @param compTypeMap 
         * @returns 
         */
        findCompMap: (compTypeMap: Map<string, TCompConstructor<Component>>) => Map<string, Component>;

        /**
         * 批量查找子节点和子节点上的组件
         * 注意：重名会导致获取的组件不对 包括 节点名字重复 和 查找名字重复
         * 使用举例：
         *      const compTypeMap = new Map<string, TCompConstructor<Component | Node>>([
         *          ['Node_0', Node],
         *          ['Node_1', UITransform],
         *          ['spr_2', Sprite],
         *      ]);
         *      const compMap = this.node.findNodeAndCompMap(compTypeMap);
         *      this._node = compMap.get('Node_0')! as Node;
         *      this._uiTrans = compMap.get('Node_1')! as UITransform;
         *      this._spr = compMap.get('spr_2')! as Sprite;
         * @param nodeAndCompTypeMap 
         * @returns 
         */
        findNodeAndCompMap: (nodeAndCompTypeMap: Map<string, TCompConstructor<Component | Node>>) => Map<string, Component | Node>;

        /**
         * 获取Node下挂载的 Component 没找到就创建
         * @param   componentType 组件类型
         */
        getOrAddComponent(className: string): Component | null;
        getOrAddComponent<T extends Component>(classConstructor: TCompConstructor<T>): T | null;
    }
}

/**
 * 通过名称查找子节点
 */
Node.prototype.findChildByName = function (name: string): Node | null {
    const nodeArr: Node[] = this.children.slice();
    let firstNode: Node | undefined = nodeArr.shift();
    while (firstNode) {
        if (firstNode.name === name) {
            // 找到了
            return firstNode;
        }
        // 把该节点的子节点都放入遍历池
        nodeArr.push(...firstNode.children);
        // 将该节点从遍历数组中移除
        firstNode = nodeArr.shift();
    }
    console.warn(`节点未找到：name = ${name}`);
    return null;
}

/**
 * 通过名称数组查找子节点，并返回子节点字典
 */
Node.prototype.findChildMapByNameArr = function (nameArr: string[]): Map<string, Node> {
    nameArr = nameArr.slice();
    const nodeMap = new Map<string, Node>();
    const childArr = this.children.slice();
    let firstNode: Node | undefined = childArr.shift();
    while (firstNode) {
        const childName = firstNode.name;
        const findIdx = nameArr.findIndex(name => name === childName);
        if (findIdx !== -1) {
            // 找到了
            nodeMap.set(childName, firstNode);
            nameArr.splice(findIdx, 1);
            if (nameArr.length === 0) {
                // 所有节点均已找到
                return nodeMap;
            }
        }
        // 把该节点的子节点都放入遍历池
        childArr.push(...firstNode.children);
        // 将该节点从遍历数组中移除
        firstNode = childArr.shift();
    }
    // 部分节点未找到
    console.warn(`未找到的节点：${nameArr.join(', ')}`);
    return nodeMap;
}

/**
 * 通过名称查找子节点上的组件
 */
Node.prototype.findCompByName = function <T extends Component>(name: string, compType: TCompConstructor<T>): T | null | undefined {
    const nodeArr: Node[] = this.children.slice();
    let firstNode: Node | undefined = nodeArr.shift();
    while (firstNode) {
        if (firstNode.name === name) {
            // 找到了
            const comp = firstNode.getComponent(compType);
            if (comp) {
                return comp;
            }
        }
        // 把该节点的子节点都放入遍历池
        nodeArr.push(...firstNode.children);
        // 将该节点从遍历数组中移除
        firstNode = nodeArr.shift();
    }
    console.warn(`组件未找到：[${name}, ${compType.name}]`);
    return null;
}

/**
 * 批量查找子节点上的组件
 */
Node.prototype.findCompMap = function (compTypeMap: Map<string, TCompConstructor<Component>>): Map<string, Component> {
    const compMap = new Map<string, Component>();
    const nodeArr: Node[] = this.children.slice();
    let firstNode: Node | undefined = nodeArr.shift();
    while (firstNode) {
        const childName = firstNode.name;
        const compType = compTypeMap.get(childName);
        if (compType) {
            // 找到同名节点
            const comp = firstNode.getComponent(compType);
            if (comp) {
                // 找到了组件
                compMap.set(childName, comp);
                compTypeMap.delete(childName);
                if (compTypeMap.size === 0) {
                    // 所有组件均已找到
                    return compMap;
                }
            }
        }
        // 把该节点的子节点都放入遍历池
        nodeArr.push(...firstNode.children);
        // 将该节点从遍历数组中移除
        firstNode = nodeArr.shift();
    }

    // 部分组件未找到
    let noFoundTipStr = '未找到的节点：';
    compTypeMap.forEach((compType, name) => {
        noFoundTipStr += `\n\t[${name}, ${compType.name}],`
    });
    console.warn(noFoundTipStr);
    return compMap;
};

/**
 * 批量查找子节点上的组件
 */
Node.prototype.findNodeAndCompMap = function (nodeAndCompTypeMap: Map<string, TCompConstructor<Component | Node>>): Map<string, Component | Node> {
    const compMap = new Map<string, Component | Node>();
    const nodeArr: Node[] = this.children.slice();
    let firstNode: Node | undefined = nodeArr.shift();
    while (firstNode) {
        const childName = firstNode.name;
        const compType = nodeAndCompTypeMap.get(childName);
        if (compType) {
            // 找到同名节点
            if (compType === Node) {
                compMap.set(childName, firstNode);
                nodeAndCompTypeMap.delete(childName);
                if (nodeAndCompTypeMap.size === 0) {
                    // 所有组件均已找到
                    return compMap;
                }
            }
            else {
                const comp = firstNode.getComponent(compType as TCompConstructor<Component>);
                if (comp) {
                    // 找到了组件
                    compMap.set(childName, comp);
                    nodeAndCompTypeMap.delete(childName);
                    if (nodeAndCompTypeMap.size === 0) {
                        // 所有组件均已找到
                        return compMap;
                    }
                }
            }
        }
        // 把该节点的子节点都放入遍历池
        nodeArr.push(...firstNode.children);
        // 将该节点从遍历数组中移除
        firstNode = nodeArr.shift();
    }

    // 部分组件未找到
    let noFoundTipStr = '未找到的节点：';
    nodeAndCompTypeMap.forEach((compType, name) => {
        noFoundTipStr += `\n\t[${name}, ${compType.name}],`
    });
    console.warn(noFoundTipStr);
    return compMap;
};

/**
 * 获取Node下挂载的 Component 没找到就创建
 */
Node.prototype.getOrAddComponent = function <T extends Component>(typeOrClassName: string | TCompConstructor<T>): T | null {
    // @ts-ignore
    return this.getComponent(typeOrClassName) ?? this.addComponent(typeOrClassName);
}